home *** CD-ROM | disk | FTP | other *** search
- /*---------------------------------------------------------------------------
- BoxViewSwitcher.m -- switch between box views using a popup list (button)
-
- Copyright (c) 1990 Doug Brenner
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 1, or (at your option)
- any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or send
- electronic mail to the the author.
-
- Implementation for the BoxViewSwitcher class. BoxViewSwitcher allows you to
- switch between a group of views using a popup button. (Something like the
- popup button on the Inspector panel of Interface Builder.)
-
- *** Using BoxViewSwitcher
-
- This is the easy part because you do everything in Interface Builder! Here
- is a step-by-step guide meant for people familiar with Interface Builder.
-
- 1. Setup: Launch IB, create a new application, create a project, and save.
-
- 2. Main window setup: You should already have one window from when you
- created the new application. This will be the main window with which people
- will interacte. On this window create one button and one box view. Within
- the box view put any controls that you want and give the box view a name.
-
- This box view will be the "default" box view that people will see when your
- application is launched.
-
- 3. Alternate window setup: Create another window. (I'll refer to this window
- as the "alternate" window and the window from step 2 as the "main" window.)
- Select and copy the box view from the main window and paste it onto this
- alternate window. Give this newly pasted box view a different name and
- update it's controls, etc., to your needs.
-
- If you want more than two alternate views, repeat the copy/paste/udpate
- process until you have all you need. BoxViewSwitcher will locate all the box
- views at the "top level" of the alternate window. (In other words, it
- doesn't descend the view hierarchy looking for box views within views.)
-
- 4. Bring in BoxViewSwitcher: Open the Classes panel and create a subclass of
- Object. (Make sure it's a subclass of object.) Rename this subclass as
- BoxViewSwitcher. Drag copies of BoxViewSwitcher.h and BoxViewSwitcher.m
- into the same folder as this Interface Builder project.
-
- Choose Parse from the Classes popdown button. (Click "Replace" since you know
- the outlets, etc., are different.)
-
- Choose Instantiate from the Classes popdown button. You should see a new
- instance (probably named BoxViewSwitcherInstance) in the objects window.
-
- Add BoxViewSwitcher.[hm] to the Files section of the Project window.
-
- 5. Attach the BoxViewSwitcher instance outlets: There are only three outlets
- that you need connect: controlButton, boxView, and boxViewWindow. (Ignore
- boxViewList and popup.)
-
- Connect the BoxViewSwitcher instance outlets as follows:
-
- Outlet Name Connect to:
- ------------- -------------------------------------------------
- controlButton button on the main window
- boxView box view on the main window
- boxViewWindow alternate window of box views (the entire window)
-
- (If you want the instantiated BoxViewSwitcher to send delegate methods [see
- setDelegate:], connect some other custom object to the delegate outlet.)
-
- 6. Make everything: Save and then make the application.
-
- *** Hints
-
- You can connect the controls in any of the box views to your own custom
- objects or other controls. These connections will be maintained even after
- the box views "move" from one window to the other.
-
- In general you want all the box views to be the same size and to have unique
- names. The alternate box view window should have "Visible at Launch Time"
- (in the Inspector panel) unchecked.
-
- You can also create menu items to select between the various box views. Just
- name the menu item the same as the desired box view title and connect the
- menu item to BoxViewSwitcher's selectBoxView: method.
-
- All three outlets (controlButton, boxView, boxViewWindow) must be connected,
- and connected to the proper classes or nothing will happen.
-
- BoxViewSwitcher assumes the alternate window is for its use only. After it
- collects all the box views, the alternate window is freed. (I.e., if you
- need a place for other things, don't put them on the alternate window.)
-
- *** Other controls and the selectBoxView: method
-
- BoxViewSwitcher only knows about one control, controlButton. This means only
- controlButton is always updated to reflect which box view is currently
- displayed. Why is this important?
-
- Let's say you attach a radio button matrix to the selectBoxView: method;
- things will look fine at first. The matrix will correctly select between the
- various box views based on which button is selected. The problem shows up
- when you use the controlButton/popup combination to select a box view.
-
- Because BoxViewSwitcher doesn't know about your radio button matrix, it
- isn't updated after a new selection is made. That is the problem.
-
- To deal with this situation, the delegate method boxViewDidSwitch:to: has
- been supplied. Use it to update other controls you might use to select
- between box views.
-
- To solve the radio button problem given above, you might try something like
- this in your delegate:
-
- #import <appkit/Button.h>
- #import <appkit/Matrix.h>
- #import <appkit/Box.h>
- #import <objc/List.h>
-
- - boxViewDidSwitch:sender
- {
- id cells, cell;
- int i;
- const char *newTitle;
-
- cells = [radioButtonMatrix cellList];
- for (i = [cells count]-1; i >= 0; i--) {
- cell = [cells objectAt:i];
- newTitle = [[sender boxView] title];
- if (strcmp ([cell title], newTitle) == 0) {
- [radioButtonMatrix selectCell:cell];
- break;
- }
- }
- return self;
- }
-
- return self;
-
- And then again, you may find the above code silly. Use it as you see fit.
-
- Doug Brenner <dbrenner@umaxc.weeg.uiowa.edu>
-
- $Header: /rpruess/apps/Remotes3.0/RCS/BoxViewSwitcher.m,v 3.0 92/09/23 22:16:54 rpruess Exp $
- -----------------------------------------------------------------------------
- $Log: BoxViewSwitcher.m,v $
- Revision 3.0 92/09/23 22:16:54 rpruess
- Checked in to RCS to get the revision number updated to 3.0.
-
- Revision 2.1 92/09/23 21:29:31 rpruess
- Updated code for NeXT System Release 3.0.
-
- Revision 2.0 91/01/22 15:24:01 rpruess
- Incorporated into Remotes at Release 2.0.
-
- Revision 2.0 91/01/22 13:13:36 rpruess
- Initial production release of Remotes-2.0.
-
- Revision 2.0 90/06/22 18:28:39 dbrenner
- Comment changes; added delegate notification in selectBoxViewTitle: (this
- required a new instance variable, delegate, and method (setDelegate:);
- added methods to return most instance variables (boxViewList, boxView,
- popup, controlButton); moved code to force the controlButton title from
- replaceBoxViewWith: to selectBoxViewTitle: (this seems more reasonable);
- added archive methods (read and write); added class method initialize
- to set the class version number.
-
- Revision 1.3 90/06/05 17:06:08 dbrenner
- Minor comment changes for automatic documentation generation.
-
- Revision 1.2 90/05/26 14:52:16 dbrenner
- Many more comments; added code to update the controlButton title after
- a switch has been made (in case things were done via another control).
-
- Revision 1.1 90/05/18 21:12:44 dbrenner
- Initial revision
-
- -----------------------------------------------------------------------------*/
-
- #import <appkit/Button.h>
- #import <appkit/Window.h>
- #import <appkit/Box.h>
- #import <appkit/PopUpList.h>
- #import <appkit/View.h>
- #import <objc/List.h>
-
- #include <string.h>
-
- #import "BoxViewSwitcher.h"
-
- @implementation BoxViewSwitcher
-
- /*---------------------------------------------------------------------------
- The methods setControlButton:, setBoxViewWindow:, and setBoxView: set the
- outlets controlButton, boxViewWindow, and boxView, respectively. (Connect
- these outlets via Interface Builder.)
-
- In all cases, if the id received is of the proper type, its value is stored;
- otherwise, the method just returns.
-
- After storing the id, the setup method is called. Since you cannot (and
- should not) depend on the order in which ids are supplied, setup is called
- every time but doesn't do anything until all three outlets are initialized.
- -----------------------------------------------------------------------------*/
- - setControlButton: anObject
- {
- if ([anObject isKindOf:[Button class]]) {
- controlButton = anObject;
- [self setup];
- }
- return self;
- }
-
- - setBoxViewWindow: anObject
- {
- if ([anObject isKindOf:[Window class]]) {
- boxViewWindow = anObject;
- [self setup];
- }
- return self;
- }
-
- - setBoxView: anObject
- {
- if ([anObject isKindOf:[Box class]]) {
- boxView = anObject;
- [self setup];
- }
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This method sets the BoxViewSwitcher's delegate to anObject.
-
- After a switch is made, the message boxViewDidSwitchTo: is sent to the delegate
- with the title of the new box view. This message is informative only; the
- return value is ignored.
- ---------------------------------------------------------------------------*/
- - setDelegate: anObject { delegate = anObject; return self;}
-
- /*---------------------------------------------------------------------------
- These methods return instance variable information.
- ---------------------------------------------------------------------------*/
- - boxViewList { return boxViewList; }
- - popup { return popup; }
- - boxView { return boxView; }
- - controlButton { return controlButton; }
-
- /*---------------------------------------------------------------------------
- This method sets everything up. In particular it does the following:
-
- Creates a popup list of possible box view titles. (The titles are from
- boxView's title and from the title of every box view in the contentView of
- boxViewWindow.)
-
- Populates boxViewList with the ids of the possible box views. (This allows
- easy searching and a provides a storage locate for the views after
- boxViewWindow is freed. [This list includes the original boxView.])
-
- Sets the popup list target/action and connects the popup to controlButton.
- (The popup list sends selectBoxView: to BoxViewSwitcher when something is
- selected, hence we set it's target and action appropriately. [These could
- possibly be over-ridden if controlButton already has a target and action.
- See the discussion under NXAttachPopUpList in the PopUpList specification.])
-
- Frees boxViewWindow after removing the box views from its view hierarchy.
- (This reduces memory requirements somewhat. It is assumed that boxViewWindow
- is nothing more than a convient place for you to design and store the
- alternate box views. Once collected into boxViewList, the window is useless
- to BoxViewSwitcher.)
- -----------------------------------------------------------------------------*/
- - setup
- {
- int i;
- id views; /* list of subviews in boxViewWindow */
- id oneView; /* one view from the above list */
-
-
- /* ---------- we need all three outlets before we can setup */
-
- if (! (controlButton && boxViewWindow && boxView))
- return self;
-
-
- /* ---------- setup popup list and populate boxViewList */
-
- popup = [[PopUpList alloc] init]; /* popup list for controlButton */
- boxViewList = [[List alloc] init]; /* we'll save the box views here */
-
- [popup addItem:[boxView title]];
- [boxViewList addObject:boxView];
-
- views = [[boxViewWindow contentView] subviews];
-
- for (i = [views count]-1; i >= 0; i--) {
- oneView = [views objectAt:i];
- if ([oneView isKindOf:[Box class]]) {
- [popup addItem:[oneView title]];
- [boxViewList addObject:oneView];
- }
- }
-
- [[popup setTarget:self] setAction:@selector(selectBoxView:)];
-
-
- /* ---------- setup controlButton */
-
- [controlButton setTitle:[boxView title]];
- NXAttachPopUpList (controlButton, popup);
-
-
- /* ---------- dettach box views from boxViewWindow and free the window */
-
- views = [[boxViewWindow contentView] subviews];
-
- for (i = [views count]-1; i >= 0; i--)
- [[views objectAt:i] removeFromSuperview];
-
- [boxViewWindow free];
- boxViewWindow = nil; /* to prevent problems */
-
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This is the primary interface for controls that have titles. (Anything that
- uses ButtonCell is a good bet, e.g., Menu, PopUpList.) This method simply
- queries the sender for its title (via [[sender selectedCell] title]) and
- uses selectBoxViewTitle: to do the real work.
-
- If BoxViewSwitch did the setup, the method is called by the popup list that
- BoxViewSwitch created during the setup method.
- -----------------------------------------------------------------------------*/
- - selectBoxView: sender
- {
- [self selectBoxViewTitle:[[sender selectedCell] title]];
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This method searches boxViewList for a box view with the same title as the
- one passed in. If a match is found, replaceBoxViewWith: is used to replace
- boxView with the found view.
-
- The controlButton title is normally updated by the popup list, but this
- method also updates it to handle the case where some other control has made
- the selection. (See the opening comments for more information about using
- other controls with BoxViewSwitcher.)
-
- After the switch, the delegate is informed about the change.
- -----------------------------------------------------------------------------*/
- - selectBoxViewTitle:(const char *) title
- {
- int i;
- id thisView;
-
- if (! boxViewList || ! title) return self;
-
- for (i = [boxViewList count]-1; i >= 0; i--) {
- thisView = [boxViewList objectAt:i];
-
- if (strcmp ([thisView title], title) == 0) {
-
- [self replaceBoxViewWith:thisView];
-
- [controlButton setTitle:[boxView title]];
-
- /* notify the delegate about the switch, if it wants to know */
-
- if ([delegate respondsTo:@selector(boxViewDidSwitch:)] == YES)
- [delegate boxViewDidSwitch:self];
-
- break;
- }
- }
-
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This method replaces boxView with aView, forces aView into the same frame as
- boxView, and asks the superview to redisplay.
-
- The frame of aView must be changed because the various view frames are most
- likely different. Keep in mind that the alternate views start out on another
- window and have unknown frames. It is also possible that boxView's window
- may have resized. (It is this latter resizing issue that really matters
- since the alternate box view frames could have been forced during setup.)
-
- It is assumed that the current boxView always has the proper frame, hence,
- its frame is forced upon aView.
-
- And a final special note for those of you using View's replaceSubview:with:
- method. Don't replace a view with itself. This is a degenerate case that
- caused me a bit of confusion. The following code, or its equivalent,
-
- [aSuperView replaceSubview:aView with:aView]
-
- results in aView being removed from aSuperView's hierarchy. (At least it
- happened at the time of this writing.) Not necessarily what you might want.
- -----------------------------------------------------------------------------*/
- - replaceBoxViewWith: aView
- {
- NXRect r;
-
- if (! boxView || ! aView) return self;
-
- if (boxView == aView || [aView isKindOf:[View class]] == NO)
- return self;
-
- [boxView getFrame:&r];
- [aView setFrame:&r];
-
- [[boxView superview] replaceSubview:boxView with:aView];
- [[aView superview] display];
- boxView = aView;
-
- return self;
- }
-
- /*---------------------------------------------------------------------------
- Frees the popup list, all the views in boxViewList, and boxViewList.
-
- The fact that all the views in boxViewList are freed seems to imply that
- BoxViewSwitcher "owns" them. (This could be considered wrong.)
- -----------------------------------------------------------------------------*/
- - free
- {
- [popup free];
- [boxViewList freeObjects];
- [boxViewList free];
- [super free];
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This method write the BoxViewSwitcher instance to the typed stream.
- ---------------------------------------------------------------------------*/
- - write:(NXTypedStream *) stream
- {
- [super write:stream];
-
- /* ---------- version 1 variables */
- NXWriteTypes (stream, "@@@", controlButton, boxViewWindow, boxView);
- NXWriteTypes (stream, "@@", popup, boxViewList);
-
- /* ---------- version 2 variables */
- NXWriteObjectReference (stream, delegate);
-
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This method reads the BoxViewSwitcher instance from the typed stream.
- ---------------------------------------------------------------------------*/
- - read: (NXTypedStream *) stream
- {
- int streamVer; /* stream class version */
-
- [super read:stream];
-
- streamVer = NXTypedStreamClassVersion (stream, "BoxViewSwitcher");
-
- /* ---------- version 1 variables */
- NXReadTypes (stream, "@@@", controlButton, boxViewWindow, boxView);
- NXReadTypes (stream, "@@", popup, boxViewList);
-
- /* ---------- version 2 variables */
- if (streamVer >= 2) NXReadTypes (stream, "@", delegate);
-
- return self;
- }
-
- /*---------------------------------------------------------------------------
- This class method sets the version number of the User class. The current
- version number is 2; version number 1 did not implement this method.
-
- This method should not be called directly.
- ---------------------------------------------------------------------------*/
- + initialize
- {
- [super initialize];
- [BoxViewSwitcher setVersion:2];
- return self;
- }
-
- @end
-